#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/completion.h>
#include <linux/types.h>
#include <linux/sched.h>

#include "log.h"

#define DEV_NAME "complete"
#define DEV_MINOR 0
#define DEV_NUM 1

struct dev_complete {
    struct cdev cdev;
    struct completion done;
};

struct dev_complete *g_dev;

static int complete_open(struct inode *inode, struct file *filp)
{
    LOGI("open");
    struct dev_complete *dev;

    dev = container_of(inode->i_cdev, struct dev_complete, cdev);
    filp->private_data = dev;

    return 0;
}

static ssize_t complete_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    LOGI("read");
    struct dev_complete *dev;
    pid_t pid;
    int res;

    dev = (struct dev_complete*)filp->private_data;
    if (*offset) {
        LOGI("eof\n");
        return 0;
    }

    pid = task_pid_nr(current);
    LOGI("%d wait for completion", pid);
    if ((res = wait_for_completion_interruptible(&dev->done)) < 0) {
        LOGI("read interrupted\n");
        return res;
    }

    if (copy_to_user(buf, "a", 1)) {
        LOGE("read fault\n");
        return -EFAULT;
    }
    *offset += 1;
    LOGI("read 1 char\n");
    return 1;
}

static ssize_t complete_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
    LOGI("write");
    struct dev_complete *dev;
    char *kbuf;

    dev = (struct dev_complete*)filp->private_data;
    kbuf = kmalloc(size+1, GFP_KERNEL);
    if (!buf) {
        LOGE("alloc write buffer\n");
        return -ENOMEM;
    }
    if (copy_from_user(kbuf, buf, size)) {
        LOGE("write fault\n");
        return -EFAULT;
    }
    kbuf[size] = '\0';
    LOGI("wrote %s\n", kbuf);
    *offset += size;
    kfree(kbuf);

    LOGI("notify completion");
    complete(&dev->done);
    return size;
}

struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = complete_open,
    .read = complete_read,
    .write = complete_write,
};

static int __init complete_init(void)
{
    LOGI("init");
    struct dev_complete *dev;
    dev_t devno;
    char buf[32];
    int res;

    dev = kmalloc(sizeof(struct dev_complete), GFP_KERNEL);
    if (!dev) {
        LOGE("alloc dev_complete");
        return -ENOMEM;
    }
    memset(dev, 0, sizeof(struct dev_complete));

    res = alloc_chrdev_region(&devno, DEV_MINOR, DEV_NUM, DEV_NAME);
    if (res) {
        LOGE("alloc devno");
        goto devno_err;
    }

    cdev_init(&dev->cdev, &fops);
    dev->cdev.owner = THIS_MODULE;
    init_completion(&dev->done);

    res = cdev_add(&dev->cdev, devno, 1);
    if (res) {
        LOGE("register chrdev %s0 failed, err: %d\n", DEV_NAME, res);
    } else {
        g_dev = dev;
        format_dev_t(buf, devno);
        LOGI("register chrdev %s0 devno %s\n", DEV_NAME, buf);
        goto out;
    }

devno_err:
    if (dev) {
        kfree(dev);
        dev = NULL;
    }
out:
    return res;
} 

static void __exit complete_exit(void) {
    if (g_dev) {
        cdev_del(&g_dev->cdev);
    }
    g_dev = NULL;
}

module_init(complete_init);
module_exit(complete_exit);

MODULE_LICENSE("GPL");